package org.activiti.engine.test.api.repository;

import org.activiti.engine.impl.test.PluggableFlowableTestCase;
import org.flowable.bpmn.exceptions.XMLException;
import org.flowable.engine.RepositoryService;
import org.flowable.engine.repository.DeploymentProperties;
import org.flowable.engine.repository.ProcessDefinition;

/* Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * @author Joram Barrez
 */
public class DeployInvalidXmlTest extends PluggableFlowableTestCase {

    @Override
    protected void setUp() throws Exception {
        super.setUp();

        processEngineConfiguration.setEnableSafeBpmnXml(true); // Needs to be enabled to test this
    }

    @Override
    protected void tearDown() throws Exception {
        processEngineConfiguration.setEnableSafeBpmnXml(false); // set back to default
        super.tearDown();
    }

    public void testDeployNonSchemaConformantXml() {
        try {
            repositoryService.createDeployment()
                    .addClasspathResource("org/activiti/engine/test/api/repository/nonSchemaConformantXml.bpmn20.xml")
                    .deploymentProperty(DeploymentProperties.DEPLOY_AS_FLOWABLE5_PROCESS_DEFINITION, Boolean.TRUE)
                    .deploy()
                    .getId();
            fail();
        } catch (XMLException e) {
            // expected exception
        }

    }

    public void testDeployWithMissingWaypointsForSequenceflowInDiagramInterchange() {
        try {
            repositoryService.createDeployment()
                    .addClasspathResource("org/activiti/engine/test/api/repository/noWayPointsForSequenceFlowInDiagramInterchange.bpmn20.xml")
                    .deploymentProperty(DeploymentProperties.DEPLOY_AS_FLOWABLE5_PROCESS_DEFINITION, Boolean.TRUE)
                    .deploy()
                    .getId();
            fail();
        } catch (XMLException e) {
            // expected exception
        }
    }

    // Need to put this in a String here, if we use a separate file, the cpu usage
    // of Eclipse skyrockets, regardless of the file is opened or not

    private static String UNSAFE_XML = "<?xml version='1.0' encoding='UTF-8'?>" +
            "<!-- Billion Laugh attacks : http://portal.sliderocket.com/CJAKM/xml-attacks -->" +
            "<!DOCTYPE lols [" +
            "<!ENTITY lol 'lol'>" +
            "<!ENTITY lol1 '&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;'>" +
            "<!ENTITY lol2 '&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;&lol1;'>" +
            "<!ENTITY lol3 '&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;'>" +
            "<!ENTITY lol4 '&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;'>" +
            "<!ENTITY lol5 '&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;'>" +
            "<!ENTITY lol6 '&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;'>" +
            "<!ENTITY lol7 '&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;'>" +
            "<!ENTITY lol8 '&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;'>" +
            "<!ENTITY lol9 '&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;'>" +
            "]>" +
            "<lolz>&lol9;</lolz>" +
            "<definitions " +
            "xmlns='http://www.omg.org/spec/BPMN/20100524/MODEL'" +
            "xmlns:activiti='http://activiti.org/bpmn'" +
            "targetNamespace='Examples'>" +
            "<process id='oneTaskProcess' name='The One Task Process'>" +
            "  <documentation>This is a process for testing purposes</documentation>" +
            " <startEvent id='theStart' />" +
            " <sequenceFlow id='flow1' sourceRef='theStart' targetRef='theTask' />" +
            " <userTask id='theTask' name='my task' />" +
            " <sequenceFlow id='flow2' sourceRef='theTask' targetRef='theEnd' />" +
            " <endEvent id='theEnd' />" +
            "</process>" +
            "</definitions>";

    // See https://activiti.atlassian.net/browse/ACT-1579?focusedCommentId=319886#comment-319886
    public void testProcessEngineDenialOfServiceAttackUsingUnsafeXmlTest() throws InterruptedException {

        // Putting this in a Runnable so we can time it out
        // Without safe xml, this would run forever
        MyRunnable runnable = new MyRunnable(repositoryService);
        Thread thread = new Thread(runnable);
        thread.start();

        long waitTime = 60000L;
        thread.join(waitTime);

        assertTrue(runnable.finished);

    }

    static class MyRunnable implements Runnable {

        public boolean finished;

        protected RepositoryService repositoryService;

        public MyRunnable(RepositoryService repositoryService) {
            this.repositoryService = repositoryService;
        }

        public void run() {
            try {
                String deploymentId = repositoryService.createDeployment().addString("test.bpmn20.xml", UNSAFE_XML)
                        .deploymentProperty(DeploymentProperties.DEPLOY_AS_FLOWABLE5_PROCESS_DEFINITION, Boolean.TRUE)
                        .deploy().getId();
                assertEquals(1, repositoryService.createProcessDefinitionQuery().singleResult());
                repositoryService.deleteDeployment(deploymentId, true);
            } catch (Exception e) {
                // Exception is expected
            } finally {
                finished = true;
            }
        }

    }

    public void testExternalEntityResolvingTest() {
        String deploymentId = repositoryService.createDeployment()
                .addClasspathResource("org/activiti/engine/test/api/repository/DeployInvalidXmlTest.testExternalEntityResolvingTest.bpmn20.xml")
                .deploymentProperty(DeploymentProperties.DEPLOY_AS_FLOWABLE5_PROCESS_DEFINITION, Boolean.TRUE)
                .deploy().getId();
        try {
            ProcessDefinition processDefinition = repositoryService.createProcessDefinitionQuery().singleResult();
            assertEquals("Test 1 2 3 null", processDefinition.getDescription());
        } finally {
            repositoryService.deleteDeployment(deploymentId, true);
        }
    }

}
